MABuild "Twist Down Lists" -classic -names -modelFar -noMALibrary -autobuild
All compiles were done on a Power Mac 7500/100 with 80 mb of memory.
All compiles were done using ETO20 MacApp 3.3.1, MPW 3.4.1.
Both debug and nodebug compiles must be done using the modified files:
UPlatformMemory.h
UPlatformMemory.cp
In order to do a debug compile you must also use the substitute files for
UObject.h
UObject.cp.
Testing:
The most extensive testing was done on the Power Mac.
Less extensive 68K testing was done on a Macintosh IIci.
All testing was done under System 7.5.3 update 2.
Partition Size:
rewrite this note
The Twist Down Lists SIZE resource sets the partition size such that the application just runs out of memory
a. when alternating open and expand all commands attempting to open the last 100Mb disk partition on my 1Gb disk results in the out of memory failure
b. when first opening all disk partitions and then attempting to do the expand all command on each of them, will result tin the out of memory failure.
Problems:
Several MacApp related problems were encountered. They are described in detail here.
1. TApplication::GetContainedObject
When testing an application, you should deliberately attempt to break it. One way of accomplishing that is to ask the application to do dumb things that no sane person would ask it to do. My attitude is, if I can think to do it, there surely is at least one other person who will also do it.
In this case, the dumb thing to do is make a script ask the application to access a document when there are no open documents. The result is a fatal access error.
The difficulty arises from the following code snippet from TApplication::GetContainedObject
else if (desiredType == cDocument && selectionForm == formName)
{
// We know we're going after a document with a
// specific name so just scan the document list.
CStr255 theDocName;
CNoGhostDocsIterator iter(this);
TDocument* aDocument = iter.FirstDocument();
selectionData.GetString(theDocName);
do
{
if ((aDocument->GetOMClass() == desiredType)
&& (aDocument->fTitle == theDocName))
result = aDocument;
aDocument = iter.NextDocument();
} while (result == NULL && iter.More());
}
As can be seen, it assumes that there is at least one document and when it asks the non-existent document to GetOMClass(), an access error results.
This problem was solved in Twist Down Lists with the following code snippet in TTwistDownLists::GetContainedObject
else if (desiredType == cDocument && selectionForm == formName)
{
//Note: MacApp 3.3.1 TApplication::GetContainedObject dose not work if a script
//attempts to get a document that does not exist. In the case where there are
//no open documents, the result is an access fault. Therefore, I'm using the
//following substitute code.
CStr255 theName;
selectionData.GetString(theName);
CNoGhostDocsIterator iter(this);
for (TDocument* aDocument = iter.FirstDocument(); iter.More(); aDocument = iter.NextDocument())
When a script asks an application to open a document and the application runs out of space in its object heap, the command will fail. Unfortunately, the failure results in a fatal access fault.
The following example script causes the problem:
tell application "Twist Down Lists"
activate
open {alias "MPW:"}
expand all document "MPW"
open {alias "General:"}
expand all document "General"
open {alias "Data2:"}
expand all document "Data2"
open {alias "Data1:"}
expand all document "Data1"
open {alias "Data:"}
expand all document "Data"
open {alias "System:"}
expand all document "System"
end tell
It is nothing but an alternating sequence of opens and expand alls. When Twist Down Lists runs out of space in the object heap, TCommandHandler::DoPerformCommand catches it and signals TODocCommand to
This is a nasty one, because now you get the fatal access fault. Something bad has happened to fReply and at this point I do not know exactly what it is but it appears that whatever the bad thing is happened to theMessage. The error generated just before the access fault is:
memWZErr = -111, /*WhichZone failed (applied to free block)*/.
However, the TServerCommand constructor sets the field fSuspendTheEvent = FALSE. If it is set to TRUE, there is no problem. I believe the default value for fSuspendTheEvent should be TRUE. If you choose to set it to TRUE, you must do it before IServerCommand is executed becaus it is IServerCommand that suspends the event.
Note that the example application Icon Edit exhibits the same behavior when a script tells it to open enough documents to cause it to run out of space in its object heap.
3. TSetPropertyEvent memory leak
Initially, I used as a template for handling the TSetPropertyEvent the code given in the Icon Edit expample, excerpted below:
case cSetColor:
{
CRGBColor newColor;
CStr255 thePrompt = "Pick a new color";
if (GetColor(kBestSystemLocation, thePrompt, fColor, newColor))
{
if (TOSADispatcher::fgDispatcher->GetDefaultTarget()->IsRecordingOn())
{
TSetPropertyEvent *appleEvent = new TSetPropertyEvent;
Unfortunately, the appleEvent is never freed and the appleEvent->Send(); results in a replyEvent being created and returned. That reply event is also not freed. This is a very clear case where object counting is very helpful. Without it, you may not discover this memory leak until well after the application has shipped. With object counting, you discover the problem very quickly. With object display, you you quickly discover that you have two Apple events that have not been freed. Then, it is a question of deciding how to solve the problem.
The following code snippet shows how to solve this problem.
case cSetColor:
{
CRGBColor newColor;
CStr255 thePrompt = "Pick a new color";
if (GetColor(kBestSystemLocation, thePrompt, fColor, newColor))
{
if (TOSADispatcher::fgDispatcher->GetDefaultTarget()->IsRecordingOn())
{
TSetPropertyEvent *appleEvent = new TSetPropertyEvent;
DoMouseUp is somewhat analogous to DoMouseCommand. The difference is that DoMouseCommand knows if the click was a first click and can ignore a first click. DoMouseUp doesn’t know anything about clicks. In the case of Twist Down Lists, a mouseDown followed by a mouseUp in a twistDownControl in a behind window results in the window being brought to the front and the twistDownControl being expanded or collapsed as the case may be. That’s a little disconcerting.
In Twist Down Lists, TView::HandleMouseDown is overridden in order to capture first click information as follows:
which is saved in the TTwistDownView field fIsFirstClick for use by TTwistDownControl::DoMouseUp where it is tested with:
if (fTwistDownView -> fIsFirstClick == FALSE)
{
Do this stuff if it isn’t a first click.
}
Essentially, the mouseUp event following a first click mouseDown event is swallowed.
HI Issues:
1. Progress Indicator and Scripting
When Twist Down Lists is driven by a script that is opening disks, the progress indicator is put up. I am ambivalent as to whether that is the right thing to do. That is particularly so in view of the fact that the stop buttton is active and if the user were to click on it, Twist Down Lists would stop reading the volume and continue on with the next script command. At least during the debugging process it is nice to have it but, for a shipping application I don’t know.
2. cWritingDirection Command
Originally, there were two checked menu items, cRightToLeft and cLeftToRight. Whichever was currently being used was checked. When the other was selected, it was checked and the other one was not checked. In other words, the application toggled between two distinct menu items.
Then I decided to use a single command and change the command name appearing in the menu depending on the current situation. That is, if the current writing direction was left-to-right, the menu would show Right-To-Left and if the current writing direction was right-to-left, the menu item would show Left-To-Right.
Having used it for a while, I no longer like it. I have to think too hard to deduce what’s really going on. I think the old way, with two menu items, was better.
Unfortunately, it is too late to change it back.
3. Window zoom and resize
When the writing direction is right-to-left, the twistDwonControls and the text should remain pinned to the right side of the window. There are two situations where this does not happen. One is the case inZoomIn in TWindow::ZoomByUser. If the user zooms out, everything works. However, when the user zooms in, the view scrolls roughly to the horizontal midpoint of the view. Usually, there is nothing to see there and the scroll bars must be used to to find anything in the window.
The other is in TWindow::ResizeByUser. If the user resizes the window and makes it larger, everything remains pinned to the right side of the window as it should. But, when the user resizes the window and makes it smaller, the the stuff doesn’t remain pinned to the right side of the window.
Neither of these problems are fatal, but they sure aren’t good human interface design.
At this point, deadlines prevent me from getting them fixed, so we ship.
They’re going to be interesting problems to fix. MacApp seems to be topLeft-centric and really doesn’t want to have anything to do with topRight. ResizeByUser will be a little tricky to handle because from my preliminary investigation, subviews of TWindow don’t appear to be told that the window was resized. It may be necessary to subclass TWindow.